package de.tu_darmstadt.gdi1.xcom.controller.ai;



import java.util.List;
import java.util.Random;
import java.util.Stack;

import de.tu_darmstadt.gdi1.framework.utils.Point;
import de.tu_darmstadt.gdi1.xcom.controller.events.ActionEnum;
import de.tu_darmstadt.gdi1.xcom.model.XcomGameConstants;
import de.tu_darmstadt.gdi1.xcom.model.elements.DirectionEnum;
import de.tu_darmstadt.gdi1.xcom.model.elements.GameElement;
import de.tu_darmstadt.gdi1.xcom.model.elements.Human;
import de.tu_darmstadt.gdi1.xcom.model.elements.Monster;

/**
 * Brain of a monster. Defines a monster's character and current plan
 *  
 * @author michael
 *
 */
public class MonsterBrain {

	// Costs of certain actions need to be known to the AI
	int COSTMOVEMENT = 3;
	int COSTATTACK = 10;
	int detectionRange = 8;
	
	
	
	Point position;    // where the brain thinks it is
	Point destination; // destination. changes if reached or unreachable
	double determination = Math.random(); // personality trait
	
	Stack<ActionEnum> plan; // stores the action sequence that leads to destination
	// if the plan is empty it means to wait for changes in the surrounding.
	// For example, an Monster that is totally locked in would do nothing
	// but as soon as the door opens it will start making a plan to run
	// out of its confinement.
	
	XcomAIGame game; // changes every turn
	private boolean idle = false;
	
	Random rand = new Random();
	
	/**
	 * Cogito ergo sum
	 * @param currentposition
	 */
	MonsterBrain(Point currentposition, XcomAIGame game) {
		position = currentposition;
		this.game = game;
	}
	
	/**
	 * Still alive? More precisely:
	 * Does the position of this brain
	 * still contain a living monster?
	 * @return monster is still there
	 */
	public boolean isAlive() {
		return containsMonster(position);
	}
	public boolean isDead() {
		return !isAlive();
	}
	
	/**
	 * The monster might have some points left to do something,
	 * but not enough to follow its plan. In this case, the
	 * monster announces that it's idle, so it won't be picked
	 * to perform an action.
	 * 
	 * @return idle
	 */
	public boolean isIdle() {
		return idle;
	}
	/**
	 * When a new round starts, all monsters will be set to
	 * not being idle
	 * 
	 * @param false
	 */
	public void setIdle(boolean i) {
		idle = i;
	}
	
	private boolean containsMonster(Point point) {
		List<GameElement> there = game.getElementsAt(point);
		for(GameElement ge : there)	if(ge instanceof Monster) return true;
		return false;
	}
	
	public void updateGame(XcomAIGame game) {
		this.game = game;
	}
	
	
	
	// pathfinding stuff

	
	
	
	
	/**
	 * Tries to pick a new destination that can
	 * be reached and store plan.
	 * This might crash the game for monsters
	 * that are locked into a 1m prison cell.
	 */
	public void makeNewPlan() {
		// try to find a path somewhere
		for(int i = 0; i < 10000 ; i++) {
			Point randomGoal = new Point(rand.nextInt(game.getMapWidth()),rand.nextInt(game.getMapHeight()));
			plan = new PathFinder().findPath(game, position, randomGoal);
			if(plan != null) {
				System.out.println("Created plan!");
				break;
			}
		}
		System.out.println("No plan could be created");
	}
		
			
	ActionEnum doSomething(XcomAIGame aiGame) {
		// protects itself against wrong usage
		if(isDead()) {
			System.out.println("Brain dead");
			return null;
		}
		
		if(isIdle()) {
			System.out.println("Brain idle");
			return ActionEnum.Idle;
		}

		// if human in front: attack
		if(game.containsHuman(getNeighbourPoint(position,getDir()))){
			if(getMonster().getPoints() >= XcomGameConstants.COST_SHOOT )
				return ActionEnum.Attack;
		}
		
		// if sideway neighbour contains human: turn
		if(game.containsHuman(getNeighbourPoint(position,getLeft(getDir()))))
			return ActionEnum.RotateLeft;
		if(game.containsHuman(getNeighbourPoint(position,getRight(getDir()))))
			return ActionEnum.RotateRight;
		if(game.containsHuman(getNeighbourPoint(position,getLeft(getLeft(getDir()))))) // behind
			return ActionEnum.RotateLeft;
		
		
		
		// if human nearby: change plan to reach human
		Human closeHuman = getCloseHuman();
		if(closeHuman != null) {
			Stack<ActionEnum> reachHuman = new PathFinder().findPath(game, position, closeHuman.getPosition());
			if(reachHuman != null) // human can be reached
				this.plan = reachHuman;
		}
			
		
		
		// plan succeeded? Make new plan
		if(plan != null)
			if(plan.isEmpty()) {
				System.out.println("Brain: Plan succeeded!");
				makeNewPlan();
			}
		
		// no human there: Maybe change plan
		// change plan, based on personality
		if(Math.random()*Math.random() > determination) {
			makeNewPlan();
		}
		
		// has no plan? make one.
		if(plan == null) makeNewPlan();
		
		// no plan possible? idle
		if(plan == null) {
			System.out.println("Not possible to make plan! :(");
			setIdle(true);
			return ActionEnum.Idle;
		}
		
		// Has plan? Execute one command		
		// if that command is possible at the moment
		// FIXME add check for obstacles.
		
		return plan.pop();
		
		
	}
	
	Point getPosition() {
		return position;
	}
	
	/**
	 * Finds the monster, this brain is linked to
	 * 
	 * @return Monster or null
	 */
	Monster getMonster() {
		 List<GameElement> stuffHere = game.getElementsAt(position);
		 for(GameElement ge : stuffHere) {
			 if(ge instanceof Monster) return (Monster)ge;
		 }
		 return null;
	}
	
	
	
	/**
	 * Return a human in detectionRange
	 * or null if none is around
	 * @return human or null
	 */
	Human getCloseHuman() {
		// Actually determines closest human
		// using manhatten distance
		// and returns it if its close enough
		Human closestHuman = null;
		
		for(int x = 0; x < game.getMapWidth(); x++) 
			for(int y = 0 ; y < game.getMapHeight(); y++) {
				List<GameElement> cellContent = game.getElementsAt(new Point(x,y));
				
				for(GameElement something : cellContent) {
					if(something instanceof Human) {
						Human humanHere = (Human)something;
						
						// first human to be found?
						if(closestHuman == null) 
							closestHuman = humanHere;
						else {
							// is this one "closer" than the other?
							// (This is a bit problamatic, given that
							// it doesn't account for obstacles in between.
							// However, the tendency to pick new destinations
							// should fix this problem over time.
							if(   manhattanDistance(position, humanHere.getPosition())
								< manhattanDistance(position, closestHuman.getPosition())
							)
								closestHuman = humanHere;
						}
						
						break; // there are no other humans here
						// and even if there were it wouldn't matter
					}
				}
				
			}
		
		// none found? Sort of impossible, since it would mean
		// that the game has already ended
		if(closestHuman == null)
			return null;
		
		// only return this human, if it is close enough
		if(manhattanDistance(position, closestHuman.getPosition())<=detectionRange)
			return closestHuman;
		else return null;
	}
	
	private int manhattanDistance(Point a, Point b) {
		return Math.abs(a.getX()-b.getX()) + Math.abs(a.getY()-b.getY());
	}
	
	public DirectionEnum getDir() {
		return getMonster().getDirection();
	}
	

	Point getNeighbourPoint(Point p, DirectionEnum side) {
		switch(side) {
		case East:	return Point.modifyXAxis(p, +1);
		case North: return Point.modifyYAxis(p, -1);
		case South: return Point.modifyYAxis(p, +1);
		case West:  return Point.modifyXAxis(p, -1);
		}
		
		// unreachable code:
		return p;
	}
	
	DirectionEnum getLeft(DirectionEnum de) {
		switch(de) {
		case East:
			return DirectionEnum.North;
		case North:
			return DirectionEnum.West;
		case South:
			return DirectionEnum.East;
		case West:
			return DirectionEnum.South;
		}
		// 	FUCK ECLIPSE! IT SUCKS! THIS CODE IS UNREACHABLE DAMMIT!!111
		return false ? null : true ? null : null;
	}
	
	DirectionEnum getRight(DirectionEnum de) {
		return getLeft(getLeft(getLeft(de))); //  ;-)
		// I just hope that a magical optimizing compiler
		// will resolve this as a look up table.
		// Hey, I can dream, can't I?
	}
}
